// libc headers

#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>

// gl/glut headers

#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>

// my includes

#include <openpcg.h>
#include <libjulia.h>

#define TEX_WIDTH 500
#define TEX_HEIGHT 500

pcg_cache_t *g_channel1 = NULL, *g_channel2 = NULL, *g_channel3 = NULL;
image_t *g_image = NULL;
int g_width = 640, g_height = 480;
int num_lattice;
GLuint g_tex;

void reshape(int w, int h) {
	g_width = w;
	g_height = h;
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, w, 0, h, -1, 1);
	glMatrixMode(GL_MODELVIEW);
}

float hetero(float x, float y, float H, float lacunarity, float octaves, float offset) {
	float v, inc;
	int i;
	
	v = offset + pcg_gnoise2(x,y);
	x *= lacunarity;
	y *= lacunarity;
	
	for (i = 1; i < octaves; i++) {
		inc = pcg_gnoise2(x,y);
		inc *= pow(1.0, -H);
		inc *= v;
		v += inc;
		x *= lacunarity;
		y *= lacunarity;
	}
	
	return v;
}

float ridged(float x, float y, float H, float lacunarity, float octaves, float offset, float gain) {
	float v, r, w;
	int i;
	
	v = pcg_gnoise2(x,y);
	if (v < 0.0) v = -v;
	v = offset - v;
	v *= v;
	r = v;
	w = 1.0;
	for (i = 0; i < octaves; i++) {
		x *= lacunarity;
		y *= lacunarity;
		w = v * gain;
		if (w > 1.0) w = 1.0;
		if (w < 0.0) w = 0.0;
		v = pcg_gnoise2(x,y);
		if (v < 0.0) v = -v;
		v = offset - v;
		v *= v;
		v *= w;
		r += v * pow(lacunarity, -H);
	}
	
	return r;
}

float bias(float x, float y, pcg_noise2_fn_t prim, float bias) {
	float v;
	int sign = 0;
	v = prim(x,y);
	if (v < 0.0) { sign = 1; v = -v; }
	v = pow(v, bias);
	if (sign) v = -v;
	return v;
}

float my_noise(float x, float y) {
	float perlin(float _x, float _y) { return pcg_fBm2(_x, _y, pcg_gnoise2, 1.0, 2.0, 4.0); }

	return perlin(x, y) ;
}

void display() {
	int i, j, k;
	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glLoadIdentity();
	
	glBindTexture(GL_TEXTURE_2D, g_tex);
	glBegin(GL_QUADS);
	glVertex2i(0, 0); glTexCoord2f(0, 0);
	glVertex2i(g_width, 0); glTexCoord2f(1, 0);
	glVertex2i(g_width, g_height); glTexCoord2f(1, 1);
	glVertex2i(0, g_height); glTexCoord2f(0, 1);
	glEnd();
	
	glutSwapBuffers();
}

void run_test() {
	struct timeval timer1, timer2;
	double elapsed;
	int i, j, k;
	
	gettimeofday(&timer1, NULL);
	
	// run some tests
	
	for (i = 0; i < 100; i++) {
		for (j = 0; j < g_width; j++) {
			for (k = 0; k < g_height; k++) {
				float v, x, y;
				x = ((float)j / g_width) * num_lattice;
				y = ((float)k / g_height) * num_lattice;
				v = my_noise(x, y);
			}
		}
	}
	
	gettimeofday(&timer2, NULL);
	elapsed = (timer2.tv_sec - timer1.tv_sec)  * 1000000 + 
		timer2.tv_usec - timer1.tv_usec;
	printf("%f\n", elapsed);
	
}

// assumes the my_cache object be allocated and initialized
void generate_texture() {
	int i;
	
	pcg_cache_fill2(g_channel1, my_noise, 0, 0.0, 0.0, num_lattice, num_lattice);
	pcg_cache_fill2(g_channel2, my_noise, 0, 0.0, 0.0, num_lattice, num_lattice);
	pcg_cache_fill2(g_channel3, my_noise, 0, 0.0, 0.0, num_lattice, num_lattice);

	for (i = 0; i < TEX_WIDTH * TEX_HEIGHT; i++) {
		g_image->data[i*3] = 0xff * g_channel1->data[i];
		g_image->data[i*3 + 1] = 0xff * g_channel2->data[i];
		g_image->data[i*3 + 2] = 0xff * g_channel3->data[i];
	}

	glBindTexture(GL_TEXTURE_2D, g_tex);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, TEX_WIDTH, TEX_HEIGHT, 0, 
		GL_RGB, GL_BYTE, g_image->data);
}

void keyboard(unsigned char key, int x, int y) {
	switch(key) {
	case '=':
	case '+':
		num_lattice <<= 1;
		break;
	case '-':
	case '_':
		num_lattice >>= 1;
		break;
	case 't':
		run_test();
		break;
	}
	
	if (num_lattice == 0) num_lattice = 1;
	
	generate_texture();
	glutPostRedisplay();
}

void init_gl() {
	glEnable(GL_TEXTURE_2D);
	
	glGenTextures(1, &g_tex);
	glBindTexture(GL_TEXTURE_2D, g_tex);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

void init(int argc, char *argv[]) {
	num_lattice = 16;
	pcg_snoise(0);
	
	/*
	g_channel1 = pcg_cache_create(TEX_WIDTH, TEX_HEIGHT, 1);
	g_channel2 = pcg_cache_create(TEX_WIDTH, TEX_HEIGHT, 1);
	g_channel3 = pcg_cache_create(TEX_WIDTH, TEX_HEIGHT, 1);
	g_image = image_create(TEX_WIDTH, TEX_HEIGHT, 1, 3);
*/

	generate_texture();
}

int main(int argc , char *argv[]) {
	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL);
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(200, 200);

	glutCreateWindow("Noise");
	
	glutDisplayFunc(display);

	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);

	init_gl();
	init(argc, argv);
	
	generate_texture();
	//glutPostRedisplay();
	glutMainLoop();
	
	return 0;
}